﻿using NGuice.Inject;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using NGuice.Inject.Internal.Util;
using System.Threading;
using System.Collections.Generic;
using NGuice.Inject.Name;
using System.Runtime.Serialization;
using NGuice.Inject.Spi;
using NGuice.Inject.Util;

namespace NGuice.Test
{
    /// <summary>
    ///这是 IBinderTest 的测试类，旨在
    ///包含所有 IBinderTest 单元测试
    ///</summary>
    [TestClass()]
    public class IBinderTest
    {
        private TestContext testContextInstance;

        /// <summary>
        ///获取或设置测试上下文，上下文提供
        ///有关当前测试运行及其功能的信息。
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region 附加测试特性
        // 
        //编写测试时，还可使用以下特性:
        //
        //使用 ClassInitialize 在运行类中的第一个测试前先运行代码
        //[ClassInitialize()]
        //public static void MyClassInitialize(TestContext testContext)
        //{
        //}
        //
        //使用 ClassCleanup 在运行完类中的所有测试后再运行代码
        //[ClassCleanup()]
        //public static void MyClassCleanup()
        //{
        //}
        //
        //使用 TestInitialize 在运行每个测试前先运行代码
        //[TestInitialize()]
        //public void MyTestInitialize()
        //{
        //}
        //
        //使用 TestCleanup 在运行完每个测试后运行代码
        //[TestCleanup()]
        //public void MyTestCleanup()
        //{
        //}
        //
        #endregion

        public class Foo { }

        public IProvider<Foo> fooProvider;

        //internal virtual IBinder CreateIBinder()
        //{
        //    // TODO: 实例化相应的具体类。
        //    IBinder target = null;
        //    return target;
        //}


        class ProviderFromBinderTestModule : IModule
        {
            private IBinderTest test;

            public ProviderFromBinderTestModule(IBinderTest test)
            {
                this.test = test;
            }

            public void Configure(IBinder binder)
            {
                test.fooProvider = binder.GetProvider<Foo>();
                try
                {
                    test.fooProvider.Get();
                }
                catch (IllegalStateException e) { /* expected */ }
            }
        }

        /// <summary>
        ///通过绑定器得到Provider的测试
        ///</summary>
        [TestMethod()]
        public void ProviderFromBinderTest()
        {
            NGuice.Inject.NGuice.CreateInjector(new ProviderFromBinderTestModule(this));
            Assert.IsNotNull(fooProvider.Get());
        }

        class MissingBindingsTestModule : AbstractModule
        {
            protected override void Configure()
            {
                GetProvider<ThreadStart>();
                Bind<IComparable>();
                RequireBinding(Key<IList<string>>.Get());
                Bind<DateTime>().AnnotatedWith(Names.Named("date"));
            }
        }

        /// <summary>
        /// 绑定各种实现类的测试
        /// </summary>
        [TestMethod]
        public void MissingBindingsTest()
        {
            try
            {
                NGuice.Inject.NGuice.CreateInjector(new MissingBindingsTestModule());
            }
            catch (CreationException e)
            {
                Assert.AreEqual(4, e.ErrorMessages.Count);
                string[] substr = new string[] {
                    "No implementation for " + typeof(IComparable).FullName + " was bound.",
                    "No implementation for " + typeof(IList<string>).ToString() + " was bound.",
                    "No implementation for System.DateTime annotated with @"
                        + typeof(NamedAttribute).Name + "(value=date) was bound.",
                    "Could not find a suitable constructor in System.Threading.ThreadStart."//委托的顺序不一样！
                };
                Asserts.AssertContains(e.Message, substr);
            }
        }

        class MissingDependencyTestModule : AbstractModule
        {
            protected override void Configure()
            {
                Bind<NeedsRunnable>();
            }
        }

        /// <summary>
        /// 依赖测试
        /// </summary>
        [TestMethod]
        public void MissingDependencyTest()
        {
            try
            {
                NGuice.Inject.NGuice.CreateInjector(new MissingDependencyTestModule());
            }
            catch (CreationException e)
            {
                Assert.AreEqual(1, e.ErrorMessages.Count);
                Asserts.AssertContains(e.Message, "No implementation for NGuice.Test.Runnable was bound.");
            }
        }

        class DanglingConstantBindingTestModule : AbstractModule
        {
            protected override void Configure()
            {
                BindConstant();
            }
        }

        /// <summary>
        /// 常数测试
        /// </summary>
        [TestMethod]
        public void DanglingConstantBindingTest()
        {
            try
            {
                NGuice.Inject.NGuice.CreateInjector(new DanglingConstantBindingTestModule());
                Assert.Fail();
            }
            catch (CreationException expected)
            {
                Asserts.AssertContains(expected.Message, "1) Missing constant value. Please call to(...).");
            }
        }

        class RecursiveBindingTestModule : AbstractModule
        {
            protected override void Configure()
            {
                Bind<Runnable>().To(typeof(Runnable));
            }
        }

        /// <summary>
        /// 绑定未实现的自身的测试
        /// </summary>
        [TestMethod]
        public void RecursiveBindingTest()
        {
            try
            {
                NGuice.Inject.NGuice.CreateInjector(new RecursiveBindingTestModule());
                Assert.Fail();
            }
            catch (CreationException expected)
            {
                Asserts.AssertContains(expected.Message, "1) Binding points to itself.");
            }
        }

        class BindingNullConstantTestModule : AbstractModule
        {
            protected override void Configure()
            {
                string none = null;
                BindConstant().AnnotatedWith(Names.Named("nullOne")).To(none);
                Bind<string>().AnnotatedWith(Names.Named("nullTwo")).ToInstance(none);
            }
        }

        /// <summary>
        /// 绑定null常数的测试
        /// </summary>
        [TestMethod]
        public void BindingNullConstantTest()
        {
            try
            {
                NGuice.Inject.NGuice.CreateInjector(new BindingNullConstantTestModule());
                Assert.Fail();
            }
            catch (CreationException expected)
            {
                Asserts.AssertContains(expected.Message,
                                     "Binding to null instances is not allowed. Use ToProvider(Providers.of(null))",
                                     "Binding to null instances is not allowed. Use ToProvider(Providers.of(null))");
            }
        }

        class ToStringOnBinderApiTestModule : AbstractModule
        {
            protected override void Configure()
            {
                Assert.AreEqual("Binder", Binder.ToString());
                Assert.AreEqual("IProvider<int>", GetProvider<int>().ToString());
                Assert.AreEqual("Provider<IList<string>>", GetProvider(Key<IList<string>>.Get()).ToString());

                Assert.AreEqual("BindingBuilder<int>", Bind<int>().ToString());
                Assert.AreEqual("BindingBuilder<int>", Bind<int>().AnnotatedWith(Names.Named("a")).ToString());
                Assert.AreEqual("ConstantBindingBuilder", BindConstant().ToString());
                Assert.AreEqual("ConstantBindingBuilder", BindConstant().AnnotatedWith(Names.Named("b")).ToString());
                Assert.AreEqual("AnnotatedElementBuilder", Binder.NewPrivateBinder().Expose(typeof(int)).ToString());
            }
        }

        /// <summary>
        /// TostringAPI的测试
        /// </summary>
        [TestMethod]
        public void ToStringOnBinderApiTest()
        {
            try
            {
                NGuice.Inject.NGuice.CreateInjector(new ToStringOnBinderApiTestModule());
                Assert.Fail();
            }
            catch (CreationException e)
            {
            }
        }

        class NothingIsSerializableInBinderApiTestModule : AbstractModule
        {
            protected override void Configure()
            {
                try
                {
                    Asserts.AssertNotSerializable(Binder);
                    Asserts.AssertNotSerializable(GetProvider<int>());
                    Asserts.AssertNotSerializable(GetProvider(Key<IList<string>>.Get()));
                    Asserts.AssertNotSerializable(Bind<int>());
                    Asserts.AssertNotSerializable(Bind<int>().AnnotatedWith(Names.Named("a")));
                    Asserts.AssertNotSerializable(BindConstant());
                    Asserts.AssertNotSerializable(BindConstant().AnnotatedWith(Names.Named("b")));
                }
                catch (SerializationException e)
                {
                    Assert.Fail(e.Message);
                }
            }
        }

        /// <summary>
        /// 非序列化的测试
        /// </summary>
        [TestMethod]
        public void NothingIsSerializableInBinderApiTest()
        {
            try
            {
                NGuice.Inject.NGuice.CreateInjector(new NothingIsSerializableInBinderApiTestModule());
                Assert.Fail();
            }
            catch (CreationException ignored)
            {
            }
        }

        class ArrayTypeCanonicalizationTestModule1 : AbstractModule
        {
            static internal string[] strings = new string[] { "A" };
            static internal int[] integers = new int[] { 1 };

            protected override void Configure()
            {
                Bind<string[]>().ToInstance(strings);
                Bind<int[]>().ToInstance(integers);
            }
        }

        class ArrayTypeCanonicalizationTestModule2 : AbstractModule
        {
            protected override void Configure()
            {
                Bind<string[]>().ToInstance(new string[] { "A" });
                Bind<string[]>().ToInstance(new string[] { "B" });
            }
        }

        class ArrayTypeCanonicalizationTestModule3 : AbstractModule
        {
            protected override void Configure()
            {
                Bind<string[]>().ToInstance(ArrayTypeCanonicalizationTestModule1.strings);
                Bind<string[]>().ToInstance(ArrayTypeCanonicalizationTestModule1.strings);
            }
        }

        /// <summary>
        /// 标准数组类型的测试
        /// </summary>
        [TestMethod]
        public void ArrayTypeCanonicalizationTest()
        {
            ArrayTypeCanonicalizationTestModule1 module1 = new ArrayTypeCanonicalizationTestModule1();
            Injector injector = NGuice.Inject.NGuice.CreateInjector(module1);
            Assert.AreSame(ArrayTypeCanonicalizationTestModule1.integers, injector.GetInstance(Key<int[]>.Get()));
            Assert.AreSame(ArrayTypeCanonicalizationTestModule1.integers, injector.GetInstance(new Key<int[]>()));
            Assert.AreSame(ArrayTypeCanonicalizationTestModule1.integers, injector.GetInstance(typeof(int[])));
            Assert.AreSame(ArrayTypeCanonicalizationTestModule1.strings, injector.GetInstance(Key<string[]>.Get()));
            Assert.AreSame(ArrayTypeCanonicalizationTestModule1.strings, injector.GetInstance(new Key<string[]>() { }));
            Assert.AreSame(ArrayTypeCanonicalizationTestModule1.strings, injector.GetInstance(typeof(string[])));

            try
            {
                //再次绑定新实例将断言失败
                NGuice.Inject.NGuice.CreateInjector(new ArrayTypeCanonicalizationTestModule2());
                Assert.Fail();
            }
            catch (CreationException expected)
            {
                Asserts.AssertContains(expected.Message,
                    "1) A binding to System.String[] was already configured");
                Asserts.AssertContains(expected.Message, "1 error");
            }

            //重复绑定相同实例将忽略
            NGuice.Inject.NGuice.CreateInjector(new ArrayTypeCanonicalizationTestModule3());
            Assert.AreSame(ArrayTypeCanonicalizationTestModule1.strings, injector.GetInstance(Key<string[]>.Get()));
            Assert.AreSame(ArrayTypeCanonicalizationTestModule1.strings, injector.GetInstance(new Key<string[]>()));
            Assert.AreSame(ArrayTypeCanonicalizationTestModule1.strings, injector.GetInstance(typeof(string[])));
        }

        class UntargettedBindingTestModule : AbstractModule
        {
            protected override void Configure()
            {
                Bind<HasProvidedBy1>();
                Bind<HasImplementedBy1>();
                Bind<HasProvidedBy2>();
                Bind<HasImplementedBy2>();
                Bind<JustAClass>();
            }
        }

        /// <summary>
        /// 没有明确目标绑定的测试：无目标的绑定应该具有ImpletedBy和ProvidedBy元注释，否则必须直接构造
        /// </summary>
        [TestMethod]
        public void UntargettedBindingTest()
        {
            Injector injector = NGuice.Inject.NGuice.CreateInjector(new UntargettedBindingTestModule());
            Assert.IsNotNull(injector.GetInstance(typeof(HasProvidedBy1)));
            Assert.IsNotNull(injector.GetInstance(typeof(HasImplementedBy1)));
            Assert.AreNotSame(typeof(HasProvidedBy2), injector.GetInstance(typeof(HasProvidedBy2)).GetType());
            Assert.AreSame(typeof(ExtendsHasImplementedBy2), injector.GetInstance(typeof(HasImplementedBy2)).GetType());
            Assert.AreSame(typeof(JustAClass), injector.GetInstance(typeof(JustAClass)).GetType());
        }

        /// <summary>
        /// 无构造函数实例注入的测试
        /// </summary>
        [TestMethod]
        public void PartialInjectorGetInstanceTest()
        {
            Injector injector = NGuice.Inject.NGuice.CreateInjector();
            try
            {
                injector.GetInstance(typeof(MissingParameter));
                Assert.Fail();
            }
            catch (ConfigurationException expected)
            {
                Asserts.AssertContains(expected.Message,
                    "1) Could not find a suitable constructor in " + typeof(NoInjectConstructor).Name);
            }
        }

        class UserReportedErrorTestModule : AbstractModule
        {
            private Message message;

            public UserReportedErrorTestModule(Message message)
            {
                this.message = message;
            }

            protected override void Configure()
            {
                AddError(message);
            }
        }

        /// <summary>
        /// 在模块中添加错误的测试
        /// </summary>
        [TestMethod]
        public void UserReportedErrorTest()
        {
            Message message = new Message(GetType(), "Whoops!");
            try
            {
                NGuice.Inject.NGuice.CreateInjector(new UserReportedErrorTestModule(message));
                Assert.Fail();
            }
            catch (CreationException expected)
            {
                Assert.AreEqual(1, expected.ErrorMessages.Count);
                foreach (Message m in expected.ErrorMessages)
                {
                    Assert.AreSame(message, m);
                }
            }
        }

        class BindingToProviderTestModule : AbstractModule
        {
            protected override void Configure()
            {
                Bind<IProvider<string>>().ToInstance(Providers.Of("A"));
            }
        }

        /// <summary>
        /// 不允许绑定到IProvider的测试
        /// </summary>
        [TestMethod]
        public void BindingToProviderTest()
        {
            try
            {
                NGuice.Inject.NGuice.CreateInjector(new BindingToProviderTestModule());
                Assert.Fail();
            }
            catch (CreationException expected)
            {
                Asserts.AssertContains(expected.Message, "1) Binding to Provider is not allowed.");
            }
        }

        class CannotBindToGuiceTypesTestModule : AbstractModule
        {
            private NamedAttribute red;

            public CannotBindToGuiceTypesTestModule(NamedAttribute red)
            {
                this.red = red;
            }

            protected override void Configure()
            {
                Bind<AbstractModule>().AnnotatedWith(red).ToProvider(Providers.Of<AbstractModule>(null));
                Bind<IBinder>().AnnotatedWith(red).ToProvider(Providers.Of<IBinder>(null));
                Bind<IBinding>().AnnotatedWith(red).ToProvider(Providers.Of<IBinding>(null));
                Bind<Injector>().AnnotatedWith(red).ToProvider(Providers.Of<Injector>(null));
                Bind<Key>().AnnotatedWith(red).ToProvider(Providers.Of<Key>(null));
                Bind<IModule>().AnnotatedWith(red).ToProvider(Providers.Of<IModule>(null));
                Bind<IProvider>().AnnotatedWith(red).ToProvider(Providers.Of<IProvider>(null));
                Bind<IScope>().AnnotatedWith(red).ToProvider(Providers.Of<IScope>(null));
                Bind<Key<string>>().ToProvider(Providers.Of<Key<String>>(null));
            }
        }

        /// <summary>
        /// 不能绑定NGuice自身类型的测试
        /// </summary>
        [TestMethod]
        public void CannotBindToGuiceTypesTest()
        {
            NamedAttribute red = Names.Named("red");
            try
            {
                NGuice.Inject.NGuice.CreateInjector(new CannotBindToGuiceTypesTestModule(red));
                Assert.Fail();
            }
            catch (CreationException expected)
            {
                Asserts.AssertContains(expected.Message,
                    "Binding to core guice framework type is not allowed: AbstractModule.",
                    "Binding to core guice framework type is not allowed: Binder.",
                    "Binding to core guice framework type is not allowed: Binding.",
                    "Binding to core guice framework type is not allowed: Injector.",
                    "Binding to core guice framework type is not allowed: Key.",
                    "Binding to core guice framework type is not allowed: Module.",
                    "Binding to Provider is not allowed.",
                    "Binding to core guice framework type is not allowed: Scope.",
                    "Binding to core guice framework type is not allowed: Key.");
            }
        }

        /// <summary>
        /// 不能绑定非泛型provider的测试
        /// </summary>
        [TestMethod]
        public void InjectRawProviderTest()
        {
            try
            {
                NGuice.Inject.NGuice.CreateInjector().GetInstance(typeof(IProvider));
                Assert.Fail();
            }
            catch (ConfigurationException expected)
            {
                Asserts.AssertContains(expected.Message,
                    "1) Cannot inject a Provider that has no type parameter",
                    "while locating " + typeof(IProvider).Name);
            }
        }

    }

    class MissingParameter
    {
        [Inject]
        public MissingParameter(NoInjectConstructor noInjectConstructor) { }
    }

    class NoInjectConstructor
    {
        private NoInjectConstructor() { }
    }

    [ProvidedBy(Value = typeof(HasProvidedBy1Provider))]
    interface HasProvidedBy1 { }

    class HasProvidedBy1Provider : IProvider<HasProvidedBy1>
    {
        class HasProvidedBy1Impl : HasProvidedBy1 { }

        public HasProvidedBy1 Get()
        {
            return new HasProvidedBy1Impl();
        }

        object IProvider.Get()
        {
            return Get();
        }
    }

    [ImplementedBy(typeof(ImplementsHasImplementedBy1))]
    interface HasImplementedBy1 { }

    class ImplementsHasImplementedBy1 : HasImplementedBy1 { }

    [ProvidedBy(Value = typeof(HasProvidedBy2Provider))]
    class HasProvidedBy2 { }

    class HasProvidedBy2Impl : HasProvidedBy2 { }

    class HasProvidedBy2Provider : IProvider<HasProvidedBy2>
    {
        public HasProvidedBy2 Get()
        {
            return new HasProvidedBy2Impl();
        }

        object IProvider.Get()
        {
            return Get();
        }
    }

    [ImplementedBy(typeof(ExtendsHasImplementedBy2))]
    class HasImplementedBy2 { }

    class ExtendsHasImplementedBy2 : HasImplementedBy2 { }

    class JustAClass { }

    public interface Runnable
    {
        void Run();
    }
    public class NeedsRunnable
    {
        [Inject]
        Runnable runnable;
    }

}
